/*
 * P3DMObjectFile.java Created on Dec 14, 2007 by M.C.Boulahiya
 */
package model;

/**
 * @author of modifications M.C.Boulahiya
 * 
 * Ajout de fonctions de chargement rapide (sans les normales et les textures).
 * En plus, l'objet est centre sur (0.0, 0.0, 0.0). 17/12
 * 
 * getBoundingBoxDiagonal donne le tier de la distance dont ont doit faire reculer
 * la camera. 18/12
 * 
 * Passage par JtsMesh pour calculer les courbures gaussiennes et moyennes. 20/12
 * Acceleration de ce calcul. 21/12
 * 
 * Les courbures sont invariables par translation et rotation mais pas par mise a 
 * l'echelle (homogene ou pas). 28/12
 * Enregistrement des courbures dans des fichiers. 28/12
 * Visualisation des courbures par des couleurs. 28/12
 * 
 * Accentuer la colorisation des objets ! 31/12
 * 
 * Selection des maximums de courbures et affichage avec des spheres 04/01
 * Disposer des normales 04/01
 * 
 * Un chargement transparent des maillages en mode text 06/01
 * 
 * Coder les signatures. 07/01
 */

/*
 * $RCSfile: ObjectFile.java,v $
 *
 * Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistribution of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistribution in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL/
 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed, licensed or
 * intended for use in the design, construction, operation or
 * maintenance of any nuclear facility.
 *
 * $Revision: 1.5 $
 * $Date: 2007/02/09 17:20:10 $
 * $State: Exp $
 */
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import javax.media.j3d.Appearance;
import javax.media.j3d.BoundingBox;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.LineAttributes;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.TexCoord2f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

import com.sun.j3d.loaders.IncorrectFormatException;
import com.sun.j3d.loaders.Loader;
import com.sun.j3d.loaders.ParsingErrorException;
import com.sun.j3d.loaders.Scene;
import com.sun.j3d.loaders.SceneBase;
import com.sun.j3d.utils.geometry.Box;
import com.sun.j3d.utils.geometry.GeometryInfo;
import com.sun.j3d.utils.geometry.NormalGenerator;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.geometry.Stripifier;



/**
 * The ObjectFile class implements the Loader interface for the Wavefront
 * .obj file format, a standard 3D object file format created for use with
 * Wavefront's Advanced Visualizer (tm) and available for purchase from
 * Viewpoint DataLabs, as well as other 3D model companies.  Object Files
 * are text based
 * files supporting both polygonal and free-form geometry (curves 
 * and surfaces).  The Java 3D .obj file loader supports a subset of the
 * file format, but it is enough to load almost all commonly available 
 * Object Files.  Free-form geometry is not supported.</p>
 *
 * The Object File tokens currently supported by this loader are:</p>
 * <code>v <i>float</i> <i>float</i> <i>float</i></code></p>
 *   <dl><dd>A single vertex's geometric position in space.  The first vertex
 *   listed in the file has index 1,
 *   and subsequent vertices are numbered sequentially.</dl></p>
 * <code>vn <i>float</i> <i>float</i> <i>float</i></code></p>
 *   <dl><dd>A normal.  The first normal in the file is index 1, and 
 *   subsequent normals are numbered sequentially.</dl></p>
 * <code>vt <i>float</i> <i>float</i></code></p>
 *   <dl><dd>A texture coordinate.  The first texture coordinate in the file is
 *   index 1, and subsequent normals are numbered sequentially.</dl></p>
 * <code>f <i>int</i> <i>int</i> <i>int</i> . . .</code></p>
 *   <dl><dd><i><b>or</b></i></dl></p>
 * <code>f <i>int</i>/<i>int</i> <i>int</i>/<i>int</i> <i>int</i>/<i>int</i> . . .</code></p>
 *   <dl><dd><i><b>or</i></b></dl></p>
 * <code>f <i>int</i>/<i>int</i>/<i>int</i> <i>int</i>/<i>int</i>/<i>int</i> <i>int</i>/<i>int</i>/<i>int</i> . . .</code></p>
 *   <dl><dd>A polygonal face.  The numbers are indexes into the arrays of
 *   vertex positions, texture coordinates, and normals respectively.
 *   There is no maximum number of vertices that a single polygon may
 *   contain.  The .obj file specification says that each face must
 *   be flat and convex, but if the TRIANGULATE flag is sent to the
 *   ObjectFile constructor, each face will be triangulated by the
 *   Java 3D Triangulator, and therefore may be concave.
 *   A number may be omitted if, for example, texture coordinates are
 *   not being defined in the model.  Numbers are normally positive
 *   indexes, but may also be negative.  An index of -1 means the last
 *   member added to the respective array, -2 is the one before that,
 *   and so on.</dl></p>
 * <code>g <i>name</i></code></p>
 *   <dl><dd>Faces defined after this token will be added to the named group.
 *   These geometry groups are returned as separated Shape3D objects
 *   attached to the parent SceneGroup.  Each named Shape3D will also
 *   be in the Hashtable returned by Scene.getNamedObjects().  It is
 *   legal to add faces to a group, switch to another group, and then 
 *   add more faces to the original group by reissuing the same name
 *   with the g token.  If faces are added to the model before the g
 *   token is seen, the faces are put into the default group called
 *   "default."</dl></p>
 * <code>s <i>int</i></code></p>
 *   <dl><dd><i><b>or</i></b></dl></p>
 * <code>s off</code></p>
 *   <dl><dd>If the vn token is not used in the file to specify vertex normals
 *   for the model, this token may be used to put faces into groups
 *   for normal calculation ("smoothing groups") in the same manner as
 *   the 'g' token
 *   is used to group faces geometrically.  Faces in the same smoothing
 *   group will have their normals calculated as if they are part of
 *   the same smooth surface.  To do this, we use the Java 3D NormalGenerator
 *   utility with the creaseAngle parameter set to PI (180 degrees - 
 *   smooth shading, no creases) or to whatever the user has set the 
 *   creaseAngle.  Faces in group 0 or 'off' use a 
 *   creaseAngle of zero, meaning there is no smoothing (the normal
 *   of the face is used at all vertices giving the surface a faceted
 *   look; there will be a
 *   crease, or "Hard Edge," between each face in group zero).  There is
 *   also an implied hard edge <i>between</i> each smoothing group, where they
 *   meet each other.</p>
 *   </p>
 *   If neither the vn nor the s token is used in the file, then normals
 *   are calculated using the creaseAngle set in the contructor.
 *   Normals are calculated on each geometry
 *   group separately, meaning there will be a hard edge between each
 *   geometry group.</dl></p>
 *   </p>
 * <code>usemtl <i>name</i></code></p>
 *   <dl><dd>The current (and subsequent) geometry groups (specified with
 *   the 'g' token) have applied
 *   to them the named material property.  The following set of material
 *   properties are available by default:</dl></p>
 *   <pre>
 *     amber           amber_trans       aqua            aqua_filter
 *     archwhite       archwhite2        bflesh          black
 *     blondhair       blue_pure         bluegrey        bluetint
 *     blugrn          blutan            bluteal         bone
 *     bone1           bone2             brass           brnhair
 *     bronze          brown             brownlips       brownskn
 *     brzskin         chappie           charcoal        deepgreen
 *     default         dkblue            dkblue_pure     dkbrown
 *     dkdkgrey        dkgreen           dkgrey          dkorange
 *     dkpurple        dkred             dkteal          emerald
 *     fgreen          flaqua            flblack         flblonde
 *     flblue_pure     flbrown           fldkblue_pure   fldkdkgrey
 *     fldkgreen       fldkgreen2        fldkgrey        fldkolivegreen
 *     fldkpurple      fldkred           flesh           fleshtransparent
 *     flgrey          fllime            flltbrown       flltgrey
 *     flltolivegreen  flmintgreen       flmustard       florange
 *     flpinegreen     flpurple          flred           fltan
 *     flwhite         flwhite1          flyellow        glass
 *     glassblutint    glasstransparent  gold            green
 *     greenskn        grey              hair            iris
 *     jetflame        lavendar          lcdgreen        lighttan
 *     lighttan2       lighttan3         lighttannew     lightyellow
 *     lime            lips              ltbrown         ltgrey
 *     meh             metal             mintgrn         muscle
 *     navy_blue       offwhite.cool     offwhite.warm   olivegreen
 *     orange          pale_green        pale_pink       pale_yellow
 *     peach           periwinkle        pink            pinktan
 *     plasma          purple            red             redbrick
 *     redbrown        redorange         redwood         rubber
 *     ruby            sand_stone        sapphire        shadow
 *     ship2           silver            skin            sky_blue
 *     smoked_glass    tan               taupe           teeth
 *     violet          white             yellow          yellow_green
 *     yellowbrt       yelloworng
 *   </pre>
 * <code>mtllib <i>filename</i></code></p>
 *   <dl><dd>Load material properties from the named file.  Materials
 *   with the same name as the predefined materials above will override
 *   the default value.  Any directory path information in (filename)
 *   is ignored.  The .mtl files are assumed to be in the same directory 
 *   as the .obj file.  If they are in a different directory, use
 *   Loader.setBasePath() (or Loader.setBaseUrl() ).  The format of the
 *   material properties files
 *   are as follows:</p>
 *   <code>newmtl <i>name</i></code></p>
 *     <dl><dd>Start the definition of a new named material property.</dl></p>
 *   <code>Ka <i>float</i> <i>float</i> <i>float</i></code></p>
 *     <dl><dd>Ambient color.</dl></p>
 *   <code>Kd <i>float</i> <i>float</i> <i>float</i></code></p>
 *     <dl><dd>Diffuse color.</dl></p>
 *   <code>Ks <i>float</i> <i>float</i> <i>float</i></code></p>
 *     <dl><dd>Specular color.</dl></p>
 *   <code>illum <i>(0, 1, or 2)</i></code></p>
 *     <dl><dd>0 to disable lighting, 1 for ambient & diffuse only (specular
 *     color set to black), 2 for full lighting.</dl></p>
 *   <code>Ns <i>float</i></code></p>
 *     <dl><dd>Shininess (clamped to 1.0 - 128.0).</dl></p>
 *   <code>map_Kd <i>filename</i></code></p>
 *     <dl><dd>Texture map.  Supports .rgb, .rgba, .int, .inta, .sgi, and
 *     .bw files in addition to those supported by
 *     <a href="../../utils/image/TextureLoader.html">TextureLoader</a>.
 *     </dl></dl></p>
 */

public class P3DMObjectFile implements Loader {
	// 0=Input file assumed good
	// 1=Input file checked for inconsistencies
	// 2=path names
	// 4=flags
	// 8=Timing Info
	// 16=Tokens
	// 32=Token details (use with 16)
	// 64=limits of model coordinates
	// 128=Curvatures
	private static final int DEBUG = 0;

	/**
	 * Flag sent to constructor.  The object's vertices will be changed
	 * so that the object is centered at (0,0,0) and the coordinate
	 * positions are all in the range of (-1,-1,-1) to (1,1,1).
	 */
	public static final int RESIZE = LOAD_SOUND_NODES << 1;

	/**
	 * Flag sent to constructor.  The Shape3D object will be created
	 * by using the GeometryInfo POLYGON_ARRAY primitive, causing
	 * them to be Triangulated by GeometryInfo.  Use
	 * this if you suspect concave or other non-behaving polygons
	 * in your model.
	 */
	public static final int TRIANGULATE = RESIZE << 1;

	/**
	 * Flag sent to constructor.  Use if the vertices in your .obj
	 * file were specified with clockwise winding (Java 3D wants
	 * counter-clockwise) so you see the back of the polygons and
	 * not the front.  Calls GeometryInfo.reverse().
	 */
	public static final int REVERSE = TRIANGULATE << 1;

	/**
	 * Flag sent to contructor.  After normals are generated the data
	 * will be analyzed to find triangle strips.  Use this if your
	 * hardware supports accelerated rendering of strips.
	 */
	public static final int STRIPIFY = REVERSE << 1;

	/**
	 * Flag sent to constructor.  The object's vertices will be changed
	 * so that the object is centered at (0,0,0).
	 */
	public static final int RECENTER = STRIPIFY << 1;

	private int flags;
	private String basePath = null;
	private URL baseUrl = null;
	private boolean fromUrl = false;
	private float radians;

	// First, lists of points are read from the .obj file into these arrays. . .
	private ArrayList<Point3f> coordList;	// Holds Point3f
	private ArrayList<TexCoord2f> texList;		// Holds TexCoord2f
	private ArrayList<Vector3f> normList;		// Holds Vector3f

	// . . . and index lists are read into these arrays.
	private ArrayList<Integer> coordIdxList;	// Holds Integer index into coordList
	private ArrayList<Integer> texIdxList;	// Holds Integer index into texList
	private ArrayList<Integer> normIdxList;	// Holds Integer index into normList

	// The length of each face is stored in this array.
	private ArrayList<Integer> stripCounts;	// Holds Integer

	// Each face's Geometry Group membership is kept here. . .
	private HashMap<Integer, String> groups;		// key=Integer index into stripCounts
	// value=String name of group
	private String curGroup;

	// . . . and Smoothing Group membership is kept here
	private HashMap<Integer, String> sGroups;		// key=Integer index into stripCounts
	// value=String name of group
	private String curSgroup;

	// The name of each group's "usemtl" material property is kept here
	private HashMap<String, String> groupMaterials;	// key=String name of Group
	// value=String name of material


	// After reading the entire file, the faces are converted into triangles.
	// The Geometry Group information is converted into these structures. . .
	private HashMap<String, ArrayList<Integer>> triGroups;		// key=String name of group
	// value=ArrayList of Integer
	//       indices into coordIdxList
	private ArrayList<Integer> curTriGroup;

	// . . . and Smoothing Group info is converted into these.
	private HashMap<String, ArrayList<Integer>> triSgroups;		// key=String name of group
	// value=ArrayList of Integer
	// indices into coordIdxList
	private ArrayList<Integer> curTriSgroup;


	// Finally, coordList, texList, and normList are converted to arrays for
	// use with GeometryInfo
	private Point3f coordArray[] = null;
	private Vector3f normArray[] = null;
	private TexCoord2f texArray[] = null;

	// Used for debugging
	private long time;

	//added by M.C.Boulahiya
	private P3DMObjectFileMaterials materials = null;
	private boolean speed;
	private boolean profileExtraction;
	private String fileName = null;
	private double[][] curvArray;
	

	void readVertex(P3DMObjectFileParser st) throws ParsingErrorException {
		Point3f p = new Point3f();

		st.getNumber();
		p.x = (float)st.nval;
		st.getNumber();
		p.y = (float)st.nval;
		st.getNumber();
		p.z = (float)st.nval;

		if ((DEBUG & 32) != 0)
			System.out.println("  (" + p.x + "," + p.y + "," + p.z + ")");

		st.skipToNextLine();

		// Add this vertex to the array
		coordList.add(p);
	} // End of readVertex


	/**
	 * readNormal
	 */
	void readNormal(P3DMObjectFileParser st) throws ParsingErrorException {
		Vector3f p = new Vector3f();

		st.getNumber();
		p.x = (float)st.nval;
		st.getNumber();
		p.y = (float)st.nval;
		st.getNumber();
		p.z = (float)st.nval;

		if ((DEBUG & 32) != 0)
			System.out.println("  (" + p.x + "," + p.y + "," + p.z + ")");

		st.skipToNextLine();

		// Add this vertex to the array
		normList.add(p);
	} // End of readNormal


	/**
	 * readTexture
	 */
	void readTexture(P3DMObjectFileParser st) throws ParsingErrorException {
		TexCoord2f p = new TexCoord2f();

		st.getNumber();
		p.x = (float)st.nval;
		st.getNumber();
		p.y = (float)st.nval;

		if ((DEBUG & 32) != 0)
			System.out.println("  (" + p.x + "," + p.y + ")");

		st.skipToNextLine();

		// Add this vertex to the array
		texList.add(p);
	} // End of readTexture


	/**
	 * readFace
	 *
	 *    Adds the indices of the current face to the arrays.
	 *
	 *    ViewPoint files can have up to three arrays:  Vertex Positions,
	 *    Texture Coordinates, and Vertex Normals.  Each vertex can
	 *    contain indices into all three arrays.
	 */
	void readFace(P3DMObjectFileParser st) throws ParsingErrorException {
		int vertIndex, texIndex = 0, normIndex = 0;
		int count = 0;

		//   There are n vertices on each line.  Each vertex is comprised
		//   of 1-3 numbers separated by slashes ('/').  The slashes may
		//   be omitted if there's only one number.

		st.getToken();

		while (st.ttype != StreamTokenizer.TT_EOL) {
			// First token is always a number (or EOL)
			st.pushBack();
			st.getNumber();
			vertIndex = (int)st.nval - 1;
			if (vertIndex < 0) vertIndex += coordList.size() + 1;
			coordIdxList.add(new Integer(vertIndex));

			// Next token is a slash, a number, or EOL.  Continue on slash
			st.getToken();
			if (st.ttype == '/') {

				// If there's a number after the first slash, read it
				st.getToken();
				if (st.ttype == StreamTokenizer.TT_WORD) {
					// It's a number
					st.pushBack();
					st.getNumber();
					texIndex = (int)st.nval - 1;
					if (texIndex < 0) texIndex += texList.size() + 1;
					texIdxList.add(new Integer(texIndex));
					st.getToken();
				}

				// Next token is a slash, a number, or EOL.  Continue on slash
				if (st.ttype == '/') {

					// There has to be a number after the 2nd slash
					st.getNumber();
					normIndex = (int)st.nval - 1;
					if (normIndex < 0) normIndex += normList.size() + 1;
					normIdxList.add(new Integer(normIndex));
					st.getToken();
				}
			}
			if ((DEBUG & 32) != 0) {
				System.out.println("  " + vertIndex + '/' + texIndex +
						'/' + normIndex);
			}
			count++;
		}

		Integer faceNum = new Integer(stripCounts.size());
		stripCounts.add(new Integer(count));

		// Add face to current groups
		groups.put(faceNum, curGroup);
		if (curSgroup != null) sGroups.put(faceNum, curSgroup);

		// In case we exited early
		st.skipToNextLine();
	} // End of readFace


	/**
	 * readPartName
	 */
	void readPartName(P3DMObjectFileParser st) {
		st.getToken();

		// Find the Material Property of the current group
		String curMat = (String)groupMaterials.get(curGroup);

		// New faces will be added to the curGroup
		if (st.ttype != P3DMObjectFileParser.TT_WORD) curGroup = "default";
		else curGroup = st.sval;
		if ((DEBUG & 32) != 0) System.out.println("  Changed to group " + curGroup);

		// See if this group has Material Properties yet
		if (groupMaterials.get(curGroup) == null) {
			// It doesn't - carry over from last group
			groupMaterials.put(curGroup, curMat);
		}

		st.skipToNextLine();
	} // End of readPartName


	/**
	 * readMaterialName
	 */
	void readMaterialName(P3DMObjectFileParser st) throws ParsingErrorException {
		st.getToken();
		if (st.ttype == P3DMObjectFileParser.TT_WORD) {
			groupMaterials.put(curGroup, new String(st.sval));
			if ((DEBUG & 32) != 0) {
				System.out.println("  Material Property " + st.sval +
						" assigned to group " + curGroup);
			}
		}
		st.skipToNextLine();
	} // End of readMaterialName


	/**
	 * loadMaterialFile
	 *
	 *	Both types of slashes are returned as tokens from our parser,
	 *	so we go through the line token by token and keep just the
	 *	last token on the line.  This should be the filename without
	 *	any directory info.
	 */
	void loadMaterialFile(P3DMObjectFileParser st) throws ParsingErrorException {
		String s = null;

		// Filenames are case sensitive
		st.lowerCaseMode(false);

		// Get name of material file (skip path)
		do {
			st.getToken();
			if (st.ttype == P3DMObjectFileParser.TT_WORD) s = st.sval;
		} while (st.ttype != P3DMObjectFileParser.TT_EOL);

		materials.readMaterialFile(fromUrl,
				fromUrl ? baseUrl.toString() : basePath, s);

		st.lowerCaseMode(true);
		st.skipToNextLine();
	} // End of loadMaterialFile


	/**
	 * readSmoothingGroup
	 */
	void readSmoothingGroup(P3DMObjectFileParser st) throws ParsingErrorException {
		st.getToken();
		if (st.ttype != P3DMObjectFileParser.TT_WORD) {
			st.skipToNextLine();
			return;
		}
		if (st.sval.equals("off")) curSgroup = "0";
		else curSgroup = st.sval;
		if ((DEBUG & 32) != 0) System.out.println("  Smoothing group " + curSgroup);
		st.skipToNextLine();
	} // End of readSmoothingGroup


	/**
	 * readFile
	 *
	 *    Read the model data from the file.
	 */
	void readFile(P3DMObjectFileParser st) throws ParsingErrorException {
		st.getToken();
		while (st.ttype != P3DMObjectFileParser.TT_EOF) {

			// Print out one token for each line
			if ((DEBUG & 16) != 0) {
				System.out.print("Token ");
				if (st.ttype == P3DMObjectFileParser.TT_EOL) System.out.println("EOL");
				else if (st.ttype == P3DMObjectFileParser.TT_WORD)
					System.out.println(st.sval);
				else System.out.println((char)st.ttype);
			}

			if (st.ttype == P3DMObjectFileParser.TT_WORD) {
				if (st.sval.equals("v")) {
					readVertex(st);
				} else if (st.sval.equals("vn")) {
					readNormal(st);
				} else if (st.sval.equals("vt")) {
					readTexture(st);
				} else if (st.sval.equals("f")) {
					readFace(st);
				} else if (st.sval.equals("fo")) {  // Not sure what the dif is
					readFace(st);
				} else if (st.sval.equals("g")) {
					readPartName(st);
				} else if (st.sval.equals("s")) {
					readSmoothingGroup(st);
				} else if (st.sval.equals("p")) {
					st.skipToNextLine();
				} else if (st.sval.equals("l")) {
					st.skipToNextLine();
				} else if (st.sval.equals("mtllib")) {
					loadMaterialFile(st);
				} else if (st.sval.equals("usemtl")) {
					readMaterialName(st);
				} else if (st.sval.equals("maplib")) {
					st.skipToNextLine();
				} else if (st.sval.equals("usemap")) {
					st.skipToNextLine();
				} else {
					throw new ParsingErrorException(
							"Unrecognized token, line " + st.lineno());
				}
			}

			st.skipToNextLine();

			// Get next token
			st.getToken();
		}
	} // End of readFile


	/**
	 * Constructor.
	 * 
	 * @param flags The constants from above or from com.sun.j3d.loaders.Loader, 
	 * possibly "or'ed" (|) together.
	 * @param radians Ignored if the vn token is present in the model (user normals 
	 * supplied).  Otherwise, crease angle to use within smoothing groups, or within 
	 * geometry groups if the s token isn't present either.
	 * @param speed boolean to choose the loading method
	 * 		false = the ancient slow load.
	 *    true = the brand new quick load.
	 */
	public P3DMObjectFile(int flags, float radians, boolean speed) {
		setFlags(flags);
		this.radians = radians;
		this.speed = speed;
	} // End of P3DMObjectFile(int, float, boolean)
	
	
	/**
	 * Constructor.  Boolean quick set to false. 
	 *
	 * @param flags The constants from above or from com.sun.j3d.loaders.Loader, 
	 * possibly "or'ed" (|) together.
	 * @param radians Ignored if the vn token is present in the model (user normals 
	 * supplied).  Otherwise, crease angle to use within smoothing groups, or within 
	 * geometry groups if the s token isn't present either.
	 */
	public P3DMObjectFile(int flags, float radians) {
		this(flags, radians, false);
	} // End of P3DMObjectFile(int, float)


	/**
	 * Constructor.  Crease Angle set to default of 44 degrees (see NormalGenerator 
	 * utility for details).  Boolean quick set to false. 
	 * @param flags The constants from above or from com.sun.j3d.loaders.Loader, 
	 * possibly "or'ed" (|) together.
	 */
	public P3DMObjectFile(int flags) {
		this(flags, -1.0f, false);
	} // End of P3DMObjectFile(int)


	/**
	 * Default constructor.  Crease Angle set to default of 44 degrees (see NormalGenerator 
	 * utility for details).  Flags set to zero (0).  Boolean quick set to false.   
	 */
	public P3DMObjectFile() {
		this(0, -1.0f, false);
	} // End of P3DMObjectFile()


	/**
	 * Takes a file name and sets the base path to the directory
	 * containing that file.
	 */
	private void setBasePathFromFilename() {
		if (this.fileName.lastIndexOf(java.io.File.separator) == -1) {
			// No path given - current directory
			setBasePath("." + java.io.File.separator);
		} else {
			setBasePath(this.fileName.substring(
					0, this.fileName.lastIndexOf(java.io.File.separator)));
		}
	} // End of setBasePathFromFilename


	/**
	 * The Object File is loaded from the .obj file specified by
	 * the filename.
	 * To attach the model to your scene, call getSceneGroup() on
	 * the Scene object passed back, and attach the returned
	 * BranchGroup to your scene graph.  For an example, see 
	 * j3d-examples/ObjLoad/ObjLoad.java.
	 */
	public Scene load(String filename) 
			throws FileNotFoundException, IncorrectFormatException, ParsingErrorException {
		this.fileName = filename + ".cur";
		setBasePathFromFilename();

		Reader reader = new BufferedReader(new FileReader(filename));
		
		if (speed)
			return quickLoad(reader);
		else
			return load(reader);
	} // End of load(String)


	private void setBaseUrlFromUrl(URL url) throws FileNotFoundException {
		String u = url.toString();
		String s;
		if (u.lastIndexOf('/') == -1) {
			s = url.getProtocol() + ":";
		} else {
			s = u.substring(0, u.lastIndexOf('/') + 1);
		}
		try {
			baseUrl = new URL(s);
		}
		catch (MalformedURLException e) {
			throw new FileNotFoundException(e.getMessage());
		}
	} // End of setBaseUrlFromUrl


	/**
	 * The object file is loaded off of the web.
	 * To attach the model to your scene, call getSceneGroup() on
	 * the Scene object passed back, and attach the returned
	 * BranchGroup to your scene graph.  For an example, see 
	 * j3d-examples/ObjLoad/ObjLoad.java.
	 */
	public Scene load(URL url) throws FileNotFoundException,
	IncorrectFormatException,
	ParsingErrorException {
		BufferedReader reader;

		if (baseUrl == null) setBaseUrlFromUrl(url);

		try {
			reader = new BufferedReader(new InputStreamReader(url.openStream()));
		}
		catch (IOException e) {
			throw new FileNotFoundException(e.getMessage());
		}
		fromUrl = true;
		if (speed)
			return quickLoad(reader);
		else
			return load(reader);
	} // End of load(URL)


	/**
	 * getLimits
	 *
	 * Returns an array of Point3f which form a bounding box around the
	 * object.  Element 0 is the low value, element 1 is the high value.
	 * See normalize() below for an example of how to use this method.
	 */
	private Point3f[] getLimits() {
		Point3f cur_vtx = new Point3f();

		// Find the limits of the model
		Point3f[] limit = new Point3f[2];
		limit[0] = new Point3f(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE);
		limit[1] = new Point3f(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE);
		for (int i = 0 ; i < coordList.size() ; i++) {

			cur_vtx = (Point3f)coordList.get(i);

			// Keep track of limits for normalization
			if (cur_vtx.x < limit[0].x) limit[0].x = cur_vtx.x;
			if (cur_vtx.x > limit[1].x) limit[1].x = cur_vtx.x;
			if (cur_vtx.y < limit[0].y) limit[0].y = cur_vtx.y;
			if (cur_vtx.y > limit[1].y) limit[1].y = cur_vtx.y;
			if (cur_vtx.z < limit[0].z) limit[0].z = cur_vtx.z;
			if (cur_vtx.z > limit[1].z) limit[1].z = cur_vtx.z;
		}

		if ((DEBUG & 64) != 0) {
			System.out.println("Model range: (" +
					limit[0].x + "," + limit[0].y + "," + limit[0].z + ") to (" +
					limit[1].x + "," + limit[1].y + "," + limit[1].z + ")");
		}

		return limit;
	} // End of getLimits



	/**
	 * Center the object and make it (-1,-1,-1) to (1,1,1).
	 */
	private void resize() {
		int i;
		Point3f cur_vtx = new Point3f();
		float biggest_dif;

		Point3f[] limit = getLimits();

		// Move object so it's centered on (0,0,0)
		Vector3f offset = new Vector3f(-0.5f * (limit[0].x + limit[1].x),
				-0.5f * (limit[0].y + limit[1].y),
				-0.5f * (limit[0].z + limit[1].z));

		if ((DEBUG & 64) != 0) {
			System.out.println("Offset amount: (" +
					offset.x + "," + offset.y + "," + offset.z + ")");
		}

		// Find the divide-by value for the normalization
		biggest_dif = limit[1].x - limit[0].x;
		if (biggest_dif < limit[1].y - limit[0].y)
			biggest_dif = limit[1].y - limit[0].y;
		if (biggest_dif < limit[1].z - limit[0].z)
			biggest_dif = limit[1].z - limit[0].z;
		biggest_dif /= 2.0f;

		for (i = 0 ; i < coordList.size() ; i++) {

			cur_vtx = coordList.get(i);
			cur_vtx.add(cur_vtx, offset);
			cur_vtx.x /= biggest_dif;
			cur_vtx.y /= biggest_dif;
			cur_vtx.z /= biggest_dif;
			//coordList.set(i, cur_vtx);
		}
	} // End of resize


	private int[] objectToIntArray(ArrayList<Integer> inList) {
		int outList[] = new int[inList.size()];
		for (int i = 0 ; i < inList.size() ; i++) {
			outList[i] = ((Integer)inList.get(i)).intValue();
		}
		return outList;
	} // End of objectToIntArray


	private Point3f[] objectToPoint3Array(ArrayList<Point3f> inList) {
		Point3f outList[] = new Point3f[inList.size()];
		for (int i = 0 ; i < inList.size() ; i++) {
			outList[i] = (Point3f)inList.get(i);
		}
		return outList;
	} // End of objectToPoint3Array



	private TexCoord2f[] objectToTexCoord2Array(ArrayList<TexCoord2f> inList) {
		TexCoord2f outList[] = new TexCoord2f[inList.size()];
		for (int i = 0 ; i < inList.size() ; i++) {
			outList[i] = (TexCoord2f)inList.get(i);
		}
		return outList;
	} // End of objectToTexCoord2Array


	private Vector3f[] objectToVectorArray(ArrayList<Vector3f> inList) {
		Vector3f outList[] = new Vector3f[inList.size()];
		for (int i = 0 ; i < inList.size() ; i++) {
			outList[i] = (Vector3f)inList.get(i);
		}
		return outList;
	} // End of objectToVectorArray


	/**
	 * Each group is a list of indices into the model's index lists,
	 * indicating the starting index of each triangle in the group.
	 * This method converts those data structures
	 * into an integer array to use with GeometryInfo.
	 */
	private int[] groupIndices(ArrayList<Integer> sourceList, ArrayList<Integer> group) {
		int indices[] = new int[group.size() * 3];
		for (int i = 0 ; i < group.size() ; i++) {
			int j = ((Integer)group.get(i)).intValue();
			indices[i * 3 + 0] = ((Integer)sourceList.get(j + 0)).intValue();
			indices[i * 3 + 1] = ((Integer)sourceList.get(j + 1)).intValue();
			indices[i * 3 + 2] = ((Integer)sourceList.get(j + 2)).intValue();
		}
		return indices;
	} // end of groupIndices


	/**
	 * smoothingGroupNormals
	 *
	 * Smoothing groups are groups of faces who should be grouped
	 * together for normal calculation purposes.  The faces are
	 * put into a GeometryInfo object and normals are calculated
	 * with a 180 degree creaseAngle (no creases) or whatever the
	 * user has specified.  The normals
	 * are then copied out of the GeometryInfo and back into
	 * P3DMObjectFile data structures.
	 */
	private void smoothingGroupNormals() {
		NormalGenerator ng =
				new NormalGenerator(radians == -1.0f ? Math.PI : radians);
		NormalGenerator ng0 = new NormalGenerator(0.0);
		normList.clear();
		normIdxList = null;
		int newNormIdxArray[] = new int[coordIdxList.size()];

		Iterator<String> e = triSgroups.keySet().iterator();
		while (e.hasNext()) {
			String curname = e.next();
			ArrayList<Integer> triList = triSgroups.get(curname);

			// Check for group with no faces
			if (triList.size() > 0) {

				GeometryInfo gi = new GeometryInfo(GeometryInfo.TRIANGLE_ARRAY);

				gi.setCoordinateIndices(groupIndices(coordIdxList, triList));
				gi.setCoordinates(coordArray);

				if (curname.equals("0")) ng0.generateNormals(gi);
				else ng.generateNormals(gi);

				// Get the generated normals and indices
				Vector3f genNorms[] = gi.getNormals();
				int genNormIndices[] = gi.getNormalIndices();

				// Now we need to copy the generated normals into P3DMObjectFile
				// data structures (normList and normIdxList).  The variable
				// normIdx is the index of the index of the normal currently
				// being put into the list.  It takes some calculation to
				// figure out the new index and where to put it.
				int normIdx = 0;
				// Repeat for each triangle in the smoothing group
				for (int i = 0 ; i < triList.size() ; i++) {

					// Get the coordIdxList index of the first index in this face
					int idx = ((Integer)triList.get(i)).intValue();

					// Repeat for each vertex in the triangle
					for (int j = 0 ; j < 3 ; j++) {

						// Put the new normal's index into the index list
						newNormIdxArray[idx + j] = normList.size();

						// Add the vertex's normal to the normal list
						normList.add(genNorms[genNormIndices[normIdx++]]);
					}
				}
			}
		}
		normIdxList = new ArrayList<Integer>(coordIdxList.size());
		for (int i = 0 ; i < coordIdxList.size() ; i++) {
			normIdxList.add(new Integer(newNormIdxArray[i]));
		}
		normArray = objectToVectorArray(normList);
	} // end of smoothingGroupNormals


	/**
	 * Each face is converted to triangles.  As each face is converted,
	 * we look up which geometry group and smoothing group the face
	 * belongs to.  The generated triangles are added to each of these
	 * groups, which are also being converted to a new triangle based format.
	 *
	 * We need to convert to triangles before normals are generated
	 * because of smoothing groups.  The faces in a smoothing group 
	 * are copied into a GeometryInfo to have their normals calculated,
	 * and then the normals are copied out of the GeometryInfo using
	 * GeometryInfo.getNormalIndices.  As part of Normal generation,
	 * the geometry gets converted to Triangles.  So we need to convert
	 * to triangles *before* Normal generation so that the normals we
	 * read out of the GeometryInfo match up with the vertex data
	 * that we sent in.  If we sent in TRIANGLE_FAN data, the normal
	 * generator would convert it to triangles and we'd read out 
	 * normals formatted for Triangle data.  This would not match up
	 * with our original Fan data, so we couldn't tell which normals
	 * go with which vertices.
	 */
	private void convertToTriangles() {
		boolean triangulate = (flags & TRIANGULATE) != 0;
		boolean textures = !texList.isEmpty() && !texIdxList.isEmpty() &&
		(texIdxList.size() == coordIdxList.size());
		boolean normals = !normList.isEmpty() && !normIdxList.isEmpty() &&
		(normIdxList.size() == coordIdxList.size());
		int numFaces = stripCounts.size();
		boolean haveSgroups = curSgroup != null;

		triGroups = new HashMap<String, ArrayList<Integer>>(50);
		if (haveSgroups) triSgroups = new HashMap<String, ArrayList<Integer>>(50);

		ArrayList<Integer> newCoordIdxList = null;
		ArrayList<Integer> newTexIdxList = null;
		ArrayList<Integer> newNormIdxList = null;

		if (triangulate) {
			GeometryInfo gi = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
			gi.setStripCounts(objectToIntArray(stripCounts));
			gi.setCoordinates(coordArray);
			gi.setCoordinateIndices(objectToIntArray(coordIdxList));
			if (textures) {
				gi.setTextureCoordinateParams(1, 2);
				gi.setTextureCoordinates(0, texArray);
				gi.setTextureCoordinateIndices(0, objectToIntArray(texIdxList));
			}
			if (normals) {
				gi.setNormals(normArray);
				gi.setNormalIndices(objectToIntArray(normIdxList));
			}
			gi.convertToIndexedTriangles();

			// Data is now indexed triangles.  Next step is to take the data
			// out of the GeometryInfo and put into internal data structures

			int coordIndicesArray[] = gi.getCoordinateIndices();

			// Fix for #4366060
			// Make sure triangulated geometry has the correct number of triangles
			int tris = 0;
			for (int i = 0 ; i < numFaces ; i++)
				tris += ((Integer)stripCounts.get(i)).intValue() - 2;

			if (coordIndicesArray.length != (tris * 3)) {
				// Model contains bad polygons that didn't triangulate into the
				// correct number of triangles.  Fall back to "simple" triangulation
				triangulate = false;
			} else {

				int texIndicesArray[] = gi.getTextureCoordinateIndices(0);
				int normIndicesArray[] = gi.getNormalIndices();

				// Convert index arrays to internal ArrayList format
				coordIdxList.clear();
				texIdxList.clear();
				normIdxList.clear();
				for (int i = 0 ; i < coordIndicesArray.length ; i++) {
					coordIdxList.add(new Integer(coordIndicesArray[i]));
					if (textures) texIdxList.add(new Integer(texIndicesArray[i]));
					if (normals) normIdxList.add(new Integer(normIndicesArray[i]));
				}
			}
		}

		if (!triangulate) {
			newCoordIdxList = new ArrayList<Integer>();
			if (textures) newTexIdxList = new ArrayList<Integer>();
			if (normals) newNormIdxList = new ArrayList<Integer>();
		}

		// Repeat for each face in the model - add the triangles from each 
		// face to the Geometry and Smoothing Groups
		int baseVertex = 0;
		for (int f = 0 ; f < numFaces ; f++) {
			int faceSize = ((Integer)stripCounts.get(f)).intValue();

			// Find out the name of the group to which this face belongs
			Integer curFace = new Integer(f);
			curGroup = (String)groups.get(curFace);

			// Change to a new geometry group, create if it doesn't exist
			curTriGroup = triGroups.get(curGroup);
			if (curTriGroup == null) {
				curTriGroup = new ArrayList<Integer>();
				triGroups.put(curGroup, curTriGroup);
			}

			// Change to a new smoothing group, create if it doesn't exist
			if (haveSgroups) {
				curSgroup = (String)sGroups.get(curFace);
				if (curSgroup == null) {
					// Weird case - this face has no smoothing group.  Happens if the
					// first 's' token comes after some faces have already been defined.
					// Assume they wanted no smoothing for these faces
					curSgroup = "0";
				}
				curTriSgroup = triSgroups.get(curSgroup);
				if (curTriSgroup == null) {
					curTriSgroup = new ArrayList<Integer>();
					triSgroups.put(curSgroup, curTriSgroup);
				}
			}

			if (triangulate) {

				// Each polygon of n vertices is now n-2 triangles
				for (int t = 0 ; t < faceSize - 2 ; t++) {

					// The groups just remember the first vertex of each triangle
					Integer triBaseVertex = new Integer(baseVertex);
					curTriGroup.add(triBaseVertex);
					if (haveSgroups) curTriSgroup.add(triBaseVertex);

					baseVertex += 3;
				}
			} else {
				// Triangulate simply
				for (int v = 0 ; v < faceSize - 2 ; v++) {
					// Add this triangle to the geometry group and the smoothing group
					Integer triBaseVertex = new Integer(newCoordIdxList.size());
					curTriGroup.add(triBaseVertex);
					if (haveSgroups) curTriSgroup.add(triBaseVertex);

					newCoordIdxList.add(coordIdxList.get(baseVertex));
					newCoordIdxList.add(coordIdxList.get(baseVertex + v + 1));
					newCoordIdxList.add(coordIdxList.get(baseVertex + v + 2));

					if (textures) {
						newTexIdxList.add(texIdxList.get(baseVertex));
						newTexIdxList.add(texIdxList.get(baseVertex + v + 1));
						newTexIdxList.add(texIdxList.get(baseVertex + v + 2));
					}

					if (normals) {
						newNormIdxList.add(normIdxList.get(baseVertex));
						newNormIdxList.add(normIdxList.get(baseVertex + v + 1));
						newNormIdxList.add(normIdxList.get(baseVertex + v + 2));
					}
				}
				baseVertex += faceSize;
			}
		}

		// No need to keep these around
		stripCounts = null;
		groups = null;
		sGroups = null;

		if (!triangulate) {
			coordIdxList = newCoordIdxList;
			texIdxList = newTexIdxList;
			normIdxList = newNormIdxList;
		}
	} // End of convertToTriangles


	private SceneBase makeScene() {
		// Create Scene to pass back
		SceneBase scene = new SceneBase();
		BranchGroup group = new BranchGroup();
		scene.setSceneGroup(group);

		boolean gen_norms = normList.isEmpty() || normIdxList.isEmpty() ||
		(normIdxList.size() != coordIdxList.size());
		boolean do_tex = !texList.isEmpty() && !texIdxList.isEmpty() &&
		(texIdxList.size() == coordIdxList.size());

		// Convert ArrayLists to arrays
		coordArray = objectToPoint3Array(coordList);
		if (!gen_norms) normArray = objectToVectorArray(normList);
		if (do_tex) texArray = objectToTexCoord2Array(texList);

		coordList.clear();
		convertToTriangles();
		
		normList.clear();
		texList.clear();
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis() - time;
			System.out.println("Convert to triangles: " + time + " ms");
			time = System.currentTimeMillis();
		}

		if ((gen_norms) && (curSgroup != null)) {
			smoothingGroupNormals();
			gen_norms = false;
			if ((DEBUG & 8) != 0) {
				time = System.currentTimeMillis() - time;
				System.out.println("Smoothing group normals: " + time + " ms");
				time = System.currentTimeMillis();
			}
		}

		NormalGenerator ng = null;
		if (gen_norms) ng = new NormalGenerator(radians);

		Stripifier strippy = null;
		if ((flags & STRIPIFY) != 0) strippy = new Stripifier();

		long t2 = 0, t3 = 0, t4 = 0;

		// Each "Group" of faces in the model will be one Shape3D
		Iterator<String> e = triGroups.keySet().iterator();
		while (e.hasNext()) {
			String curname = e.next();
			ArrayList<Integer> triList = triGroups.get(curname);

			// Check for group with no faces
			if (triList.size() > 0) {

				GeometryInfo gi = new GeometryInfo(GeometryInfo.TRIANGLE_ARRAY);

				gi.setCoordinateIndices(groupIndices(coordIdxList, triList));
				gi.setCoordinates(coordArray);

				if (do_tex) {
					gi.setTextureCoordinateParams(1, 2);
					gi.setTextureCoordinates(0, texArray);
					gi.setTextureCoordinateIndices(0, groupIndices(texIdxList, triList));
				}

				if ((DEBUG & 8) != 0) time = System.currentTimeMillis();
				if (gen_norms) {
					if ((flags & REVERSE) != 0) gi.reverse();
					ng.generateNormals(gi);
					if ((DEBUG & 8) != 0) {
						t2 += System.currentTimeMillis() - time;
						System.out.println("Generate normals: " + t2 + " ms");
						time = System.currentTimeMillis();
					}
				} else {
					gi.setNormalIndices(groupIndices(normIdxList, triList));
					gi.setNormals(normArray);
					if ((flags & REVERSE) != 0) gi.reverse();
				}

				if ((flags & STRIPIFY) != 0) {
					strippy.stripify(gi);
					if ((DEBUG & 8) != 0) {
						t3 += System.currentTimeMillis() - time;
						System.out.println("Stripify: " + t3 + " ms");
						time = System.currentTimeMillis();
					}
				}

				// Put geometry into Shape3d
				Shape3D shape = new Shape3D();

				shape.setGeometry(gi.getGeometryArray(true, true, false));

				String matName = (String)groupMaterials.get(curname);
				materials.assignMaterial(matName, shape);

				group.addChild(shape);
				scene.addNamedObject(curname, shape);

				if ((DEBUG & 8) != 0) {
					t4 += System.currentTimeMillis() - time;
					System.out.println("Shape 3D: " + t4 + " ms");
					time = System.currentTimeMillis();
				}
			}
		}

		return scene;
	} // end of makeScene


	/**
	 * The Object File is loaded from the already opened file.
	 * To attach the model to your scene, call getSceneGroup() on
	 * the Scene object passed back, and attach the returned
	 * BranchGroup to your scene graph.  For an example, see 
	 * j3d-examples/ObjLoad/ObjLoad.java.
	 */
	public Scene load(Reader reader) 
			throws FileNotFoundException, IncorrectFormatException, ParsingErrorException {
		// P3DMObjectFileParser does lexical analysis
		P3DMObjectFileParser st = new P3DMObjectFileParser(reader);

		coordList = new ArrayList<Point3f>();
		texList = new ArrayList<TexCoord2f>();
		normList = new ArrayList<Vector3f>();
		coordIdxList = new ArrayList<Integer>();
		texIdxList = new ArrayList<Integer>();
		normIdxList = new ArrayList<Integer>();
		groups = new HashMap<Integer, String>(50);
		curGroup = "default";
		sGroups = new HashMap<Integer, String>(50);
		curSgroup = null;
		stripCounts = new ArrayList<Integer>();
		groupMaterials = new HashMap<String, String>(50);
		groupMaterials.put(curGroup, "default");
		materials = new P3DMObjectFileMaterials();

		time = 0L;
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis();
		}
		
		readFile(st);
		
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis() - time;
			System.out.println("Read file: " + time + " ms");
			time = System.currentTimeMillis();
		}
	
		if ((flags & RESIZE) != 0) resize();
		
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis() - time;
			System.out.println("resize: " + time + " ms");
			time = System.currentTimeMillis();
		}

		return makeScene();
	} // End of load(Reader)

	
	/**
	 * For an .obj file loaded from a URL, set the URL where associated files
	 * (like material properties files) will be found.
	 * Only needs to be called to set it to a different URL
	 * from that containing the .obj file.
	 */
	public void setBaseUrl(URL url) {
		baseUrl = url;
	} // End of setBaseUrl


	/**
	 * Return the URL where files associated with this .obj file (like
	 * material properties files) will be found.
	 */
	public URL getBaseUrl() {
		return baseUrl;
	} // End of getBaseUrl


	/**
	 * Set the path where files associated with this .obj file are
	 * located.
	 * Only needs to be called to set it to a different directory
	 * from that containing the .obj file.
	 */
	public void setBasePath(String pathName) {
		basePath = pathName;
		if (basePath == null || basePath == "")
			basePath = "." + java.io.File.separator;
		basePath = basePath.replace('/', java.io.File.separatorChar);
		basePath = basePath.replace('\\', java.io.File.separatorChar);
		if (!basePath.endsWith(java.io.File.separator))
			basePath = basePath + java.io.File.separator;
	} // End of setBasePath


	/**
	 * Return the path where files associated with this .obj file (like material
	 * files) are located.
	 */
	public String getBasePath() {
		return basePath;
	} // End of getBasePath


	/**
	 * Set parameters for loading the model.
	 * Flags defined in Loader.java are ignored by the P3DMObjectFile Loader
	 * because the .obj file format doesn't include lights, fog, background,
	 * behaviors, views, or sounds.  However, several flags are defined
	 * specifically for use with the P3DMObjectFile Loader (see above).
	 */
	public void setFlags(int flags) {
		this.flags = flags;
		if ((DEBUG & 4) != 0) System.out.println("Flags = " + flags);
	} // End of setFlags


	/**
	 * Get the parameters currently defined for loading the model.
	 * Flags defined in Loader.java are ignored by the P3DMObjectFile Loader
	 * because the .obj file format doesn't include lights, fog, background,
	 * behaviors, views, or sounds.  However, several flags are defined
	 * specifically for use with the P3DMObjectFile Loader (see above).
	 */
	public int getFlags() {
		return flags;
	} // End of getFlags

	
	// Added by M.C.Boulahiya
	/**
	 * The Object File is loaded from the already opened file.
	 * To attach the model to your scene, call getSceneGroup() on
	 * the Scene object passed back, and attach the returned
	 * BranchGroup to your scene graph.
	 */
	public Scene quickLoad(Reader reader) 
			throws FileNotFoundException, IncorrectFormatException, ParsingErrorException {
		// P3DMObjectFileParser does lexical analysis
		P3DMObjectFileParser st = new P3DMObjectFileParser(reader);

		coordList = new ArrayList<Point3f>();
		normList = new ArrayList<Vector3f>();
		coordIdxList = new ArrayList<Integer>();
		normIdxList = new ArrayList<Integer>();
		groups = new HashMap<Integer, String>(50);
		curGroup = "default";
		sGroups = new HashMap<Integer, String>(50);
		curSgroup = null;
		stripCounts = new ArrayList<Integer>();
		groupMaterials = new HashMap<String, String>(50);
		groupMaterials.put(curGroup, "default");
		materials = new P3DMObjectFileMaterials();
		
		time = 0L;
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis();
		}
		
		quickReadFile(st);
		
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis() - time;
			System.out.println("Read file: " + time + " ms");
			time = System.currentTimeMillis();
		}
	
		if ((flags & RECENTER) != 0) recenter();
		
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis() - time;
			System.out.println("recenter: " + time + " ms");
			time = System.currentTimeMillis();
		}
		
		return quickMakeScene();
	} // End of quickLoad


	/**
	 * quickReadFile
	 *
	 *    Read some model (coordinates) data from the file.
	 */
	void quickReadFile(P3DMObjectFileParser st) throws ParsingErrorException {
		st.getToken();
		while (st.ttype != P3DMObjectFileParser.TT_EOF) {

			// Print out one token for each line
			if ((DEBUG & 16) != 0) {
				System.out.print("Token ");
				if (st.ttype == P3DMObjectFileParser.TT_EOL) System.out.println("EOL");
				else if (st.ttype == P3DMObjectFileParser.TT_WORD)
					System.out.println(st.sval);
				else System.out.println((char)st.ttype);
			}

			if (st.ttype == P3DMObjectFileParser.TT_WORD) {
				if (st.sval.equals("v")) {
					readVertex(st);
				} else if (st.sval.equals("vn")) {
					readNormal(st);
				} else if (st.sval.equals("vt")) {
					st.skipToNextLine();
				} else if (st.sval.equals("f")) {
					quickReadFace(st);
				} else if (st.sval.equals("fo")) {  // Not sure what the dif is
					quickReadFace(st);
				} else if (st.sval.equals("g")) {
					readPartName(st);
				} else if (st.sval.equals("s")) {
					readSmoothingGroup(st);
				} else if (st.sval.equals("p")) {
					st.skipToNextLine();
				} else if (st.sval.equals("l")) {
					st.skipToNextLine();
				} else if (st.sval.equals("mtllib")) {
					st.skipToNextLine();
				} else if (st.sval.equals("usemtl")) {
					st.skipToNextLine();
				} else if (st.sval.equals("maplib")) {
					st.skipToNextLine();
				} else if (st.sval.equals("usemap")) {
					st.skipToNextLine();
				} else {
					throw new ParsingErrorException("Unrecognized token, line " + st.lineno());
				}
			}

			st.skipToNextLine();

			// Get next token
			st.getToken();
		}
	} // End of quickReadFile

	
	/**
	 * quickReadFace
	 *
	 *    Adds the coordinates indices of the current face to the arrays.
	 *
	 *    ViewPoint files can have up to three arrays:  Vertex Positions,
	 *    Texture Coordinates, and Vertex Normals.  Each vertex can
	 *    contain indices into all three arrays.
	 */
	void quickReadFace(P3DMObjectFileParser st) throws ParsingErrorException {
		int vertIndex, normIndex = 0, count = 0;

		//   There are n vertices on each line.  Each vertex is comprised
		//   of 1-3 numbers separated by slashes ('/').  The slashes may
		//   be omitted if there's only one number.

		st.getToken();

		while (st.ttype != StreamTokenizer.TT_EOL) {
			// First token is always a number (or EOL)
			st.pushBack();
			st.getNumber();
			vertIndex = (int)st.nval - 1;
			if (vertIndex < 0) vertIndex += coordList.size() + 1;
			coordIdxList.add(new Integer(vertIndex));
			
			// Next token is a slash, a number, or EOL.  Continue on slash
			st.getToken();
			if (st.ttype == '/') {
				// If there's a number after the first slash, read it
				st.getToken();
				if (st.ttype == StreamTokenizer.TT_WORD) {
					// It's a number
					st.pushBack();
					st.getNumber();
					st.getToken();
				}

				// Next token is a slash, a number, or EOL.  Continue on slash
				if (st.ttype == '/') {
					// There has to be a number after the 2nd slash
					st.getNumber();
					normIndex = (int)st.nval - 1;
					if (normIndex < 0) normIndex += normList.size() + 1;
					normIdxList.add(new Integer(normIndex));
					st.getToken();
				}
			}
			
			if ((DEBUG & 32) != 0) {
				System.out.println("  " + vertIndex + '/' + '/' + normIndex);
			}
			count++;			
		}

		Integer faceNum = new Integer(stripCounts.size());
		stripCounts.add(new Integer(count));

		// Add face to current groups
		groups.put(faceNum, curGroup);
		if (curSgroup != null) sGroups.put(faceNum, curSgroup);
		
		// In case we exited early
		st.skipToNextLine();
	} // End of quickReadFace
	
	
	/**
	 * Center the object on (0,0,0).
	 */
	private void recenter() {
		Point3d barryPoint = new Point3d();
		// Calculer le barycentre de l'objet :
		for (Point3f paux : coordList) {
			barryPoint.add(new Point3d(paux));
		}
		barryPoint.scale(1. / coordList.size());
		
		// Transformer les coordonnees :
		Transform3D barryTransform = new Transform3D();
		Point3d negateBarry = new Point3d();
		negateBarry.negate(barryPoint);
		barryTransform.setTranslation(new Vector3d(negateBarry));
		
		coordArray = new Point3f[coordList.size()];
		for (int i = 0; i < coordList.size(); i++) {
			coordArray[i] =  new Point3f();
			barryTransform.transform(coordList.get(i), coordArray[i]);
		}
		
		coordList.clear();
	} // End of recenter

	
	private SceneBase quickMakeScene() {
		boolean gen_norms = normList.isEmpty() || normIdxList.isEmpty() || 
					(normIdxList.size() != coordIdxList.size());
		
		// Convert ArrayLists to arrays
		// moved to recenter
		if (!gen_norms) normArray = objectToVectorArray(normList);		
		
		quickConvertToTriangles();
		normList.clear();
		
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis() - time;
			System.out.println("Convert to triangles: " + time + " ms");
			time = System.currentTimeMillis();
		}
		
		if ((gen_norms) && (curSgroup != null)) {
			smoothingGroupNormals();
			gen_norms = false;
			if ((DEBUG & 8) != 0) {
				time = System.currentTimeMillis() - time;
				System.out.println("Smoothing group normals: " + time + " ms");
				time = System.currentTimeMillis();
			}
		}
		
		if (this.fromUrl) {
			//TODO test curvatures file from URL and decide to create them or not!
		} else {
			// test if filename.cur exists ?
			File f = new File(this.fileName);
			if (f.exists()) {
				readCurvatures();
			} else {
				createCurvatures();
			}
		}
		
		// Create Scene to pass back
		SceneBase scene = new SceneBase();
		BranchGroup group = new BranchGroup();
		scene.setSceneGroup(group);
		
		//Put geometry into Shape3d
		Shape3D shape;
		BoundingBox boundingBox = this.getBoundigBox();
		System.out.println(boundingBox);
		Point3d upper = new Point3d(), lower = new Point3d();
		boundingBox.getLower(lower);
		boundingBox.getUpper(upper);
		Point3d upper2 = new Point3d(upper), lower2 = new Point3d(lower);
		upper2.setX(upper2.x / 5);
		lower2.setX(lower2.x / 5);
		BoundingBox boundingBox2 = new BoundingBox(lower2, upper2);
		int[] maxMeanCurvIndices = new int [400]; 
		int[] maxGaussianCurvIndices = new int [400]; 
		
		// Each "Group" of faces in the model will be one Shape3D
		Iterator<String> e = triGroups.keySet().iterator();
		while (e.hasNext()) {
			String curname = e.next();
			ArrayList<Integer> triList = triGroups.get(curname);
			
			// Check for group with no faces
			if (triList.size() > 0) {
				GeometryInfo gi = new GeometryInfo(GeometryInfo.TRIANGLE_ARRAY);

				gi.setCoordinateIndices(groupIndices(coordIdxList, triList));
				gi.setCoordinates(coordArray);
				
				if (this.profileExtraction) {
					for (int i = 0; i < this.coordArray.length; i++) {
						if (!boundingBox2.intersect(new Point3d(this.coordArray[i]))) {
							this.curvArray[i][1] = 0;
							this.curvArray[i][0] = 0;
						}
					}
					
					this.profileExtraction = false;
				}
				
				
				// stats
				//TODO un mix des deux courbures ?
				double min = Double.MAX_VALUE, max = Double.MIN_VALUE, add = 0, moy;
				for (double[] curvature : this.curvArray) {
					if (min > curvature[0]) min = curvature[0];
					if (max < curvature[0]) max = curvature[0];
					add += curvature[0];
				}
				moy= add/this.curvArray.length;
				//System.out.println("min= "+min+" max= "+max+" add= "+add+" moy= "+moy);
				float[] colors1 = new float[this.curvArray.length * 3];
				//if (min < 0) min = -min;
				for (int i = 0; i < colors1.length; i+=3) {
					if (this.curvArray[i/3][0] > moy) {
						colors1[i+1] = 0.1f+(float) ((this.curvArray[i/3][0]+moy)/(moy+max))/2;
					} else {
						colors1[i+1] = 0.f;
					}
					colors1[i] = 0.f;
					colors1[i+2] = 0.f;
				}
				min = Double.MAX_VALUE; max = Double.MIN_VALUE; add = 0;
				for (int i = 0; i < colors1.length; i+=3) {
					if (min > colors1[i+1]) min = colors1[i+1];
					if (max < colors1[i+1]) max = colors1[i+1];
					add += colors1[i+1];
				}
				moy= add/this.curvArray.length/3;
				//System.out.println("min= "+min+" max= "+max+" add= "+add+" moy= "+moy);
				for (int i = 0; i < colors1.length; i++) {
					colors1[i] = 1 - colors1[i];
				}

				double min2 = Double.MAX_VALUE, max2 = Double.MIN_VALUE, add2 = 0, moy2;
				for (double[] curvature : this.curvArray) {
					if (min2 > curvature[1]) min2 = curvature[1];
					if (max2 < curvature[1]) max2 = curvature[1];
					add2 += curvature[1];
				}
				moy2= add2/this.curvArray.length;
				//System.out.println("min2= "+min2+" max2= "+max2+" add2= "+add2+" moy2= "+moy2);
				float[] colors2 = new float[this.curvArray.length * 3];
				for (int i = 0; i < colors2.length; i+=3) {
					if (this.curvArray[i/3][1] > moy2) {
						colors2[i] = 0.1f+(float) ((this.curvArray[i/3][1]+moy2)/(moy2+max2))/2;
					} else {
						colors2[i] = 0.f;
					}
					colors2[i+1] = 0.f;
					colors2[i+2] = 0.f;
				}
				min2 = Double.MAX_VALUE; max2 = Double.MIN_VALUE; add2 = 0;
				for (int i = 0; i < colors2.length; i+=3) {
					if (min2 > colors2[i]) min2 = colors2[i];
					if (max2 < colors2[i]) max2 = colors2[i];
					add2 += colors2[i];
				}
				moy2= add2/this.curvArray.length/3;
				//System.out.println("min2= "+min2+" max2= "+max2+" add2= "+add2+" moy2= "+moy2);
				for (int i = 0; i < colors2.length; i++) {
					colors2[i] = 1 - colors2[i];
				}
				
				double maximum;
				// Traitement des courbures !
				for (int nbMaximums = 0; nbMaximums < maxGaussianCurvIndices.length; nbMaximums++) {
					maximum = Double.MIN_VALUE;
					for (int i = 0; i < this.curvArray.length; i++) {
						if (this.curvArray[i][0] > maximum) {
							maxGaussianCurvIndices[nbMaximums] = i;
							maximum = this.curvArray[i][0];
						}
					}
					BoundingSphere bs = 
						new BoundingSphere(new Point3d(this.coordArray[maxGaussianCurvIndices[nbMaximums]]), 0.5);
					//System.out.println("Maximum = "+this.curvatures[maxIndices[nbMaximums]][1]);
					for (int i = 0; i < this.coordArray.length; i++) {
						if (bs.intersect(new Point3d(this.coordArray[i]))) { 
							//TODO tester si la courbure est differente de 0 ?!?
							//System.out.println("Elimination de n°"+i+" : "+this.coordArray[i]+" de courbure : "+this.curvatures[i][1]);
							colors2[3*i]=1.f;
							colors2[3*i+1]=1.f;
							colors2[3*i+2]=1.f;
							this.curvArray[i][0] = 0;
						}
					}
				}
				for (int nbMaximums = 0; nbMaximums < maxMeanCurvIndices.length; nbMaximums++) {
					maximum = Double.MIN_VALUE;
					for (int i = 0; i < this.curvArray.length; i++) {
						if (this.curvArray[i][1] > maximum) {
							maxMeanCurvIndices[nbMaximums] = i;
							maximum = this.curvArray[i][1];
						}
					}
					BoundingSphere bs = new BoundingSphere(new Point3d(this.coordArray[maxMeanCurvIndices[nbMaximums]]), 0.5);
					//System.out.println("Maximum = "+this.curvatures[maxIndices[nbMaximums]][1]);
					for (int i = 0; i < this.coordArray.length; i++) {
						if (bs.intersect(new Point3d(this.coordArray[i]))) { 
							//TODO tester si la courbure est differente de 0 ?!?
							//System.out.println("Elimination de n°"+i+" : "+this.coordArray[i]+" de courbure : "+this.curvatures[i][1]);
							colors2[3*i]=1.f;
							colors2[3*i+1]=1.f;
							colors2[3*i+2]=1.f;
							this.curvArray[i][1] = 0;
						}
					}
				}
				
				float[] colors3 = new float[this.curvArray.length * 3];
				for (int i = 0; i < colors3.length; i+=3) {
					colors3[i] = colors2[i];
					colors3[i+1] = colors1[i+1];
					colors3[i+2] = 1.f;
				}
				gi.setColorIndices(groupIndices(coordIdxList, triList));
				gi.setColors3(colors3);
				
				if ((DEBUG & 8) != 0) time = System.currentTimeMillis();
				if (gen_norms) {
					if ((flags & REVERSE) != 0) gi.reverse();
					NormalGenerator ng = null;
					if (gen_norms) ng = new NormalGenerator(radians);
					ng.generateNormals(gi);
					if ((DEBUG & 8) != 0) {
						time = System.currentTimeMillis() - time;
						System.out.println("Generate normals: " + time + " ms");
						time = System.currentTimeMillis();
					}
				} else {
					gi.setNormalIndices(groupIndices(normIdxList, triList));
					gi.setNormals(normArray);
					if ((flags & REVERSE) != 0) gi.reverse();
				}
				
				shape = new Shape3D();
				shape.setGeometry(gi.getGeometryArray(true, true, false));
				
				PolygonAttributes pa = new PolygonAttributes();
//				pa.setPolygonMode(PolygonAttributes.POLYGON_POINT);
//				pa.setPolygonMode(PolygonAttributes.POLYGON_LINE);
				pa.setPolygonMode(PolygonAttributes.POLYGON_FILL);
				Appearance ap = new Appearance();
				ap.setPolygonAttributes(pa);
				shape.setAppearance(ap);
				
				group.addChild(shape);
				scene.addNamedObject(curname, shape);

				if ((DEBUG & 8) != 0) {
					time = System.currentTimeMillis() - time;
					System.out.println("Shape 3D: " + time + " ms");
					time = System.currentTimeMillis();
				}
				
				
				// Afficher les normales				
//				final int size = 1;
//				Vector3f[] normals = gi.getNormals();
//				LineArray la = 
//					new LineArray(this.coordArray.length*2,LineArray.COORDINATES);
//				int j = 0;
//				for (int i = 0; i < this.coordArray.length; i++) {
//					la.setCoordinate(j, this.coordArray[i]);
//					j++;
//					Point3f p = new Point3f(normals[i]);
//					la.setCoordinate(j, new Point3f(this.coordArray[i].x+p.x/size,
//							this.coordArray[i].y+p.y/size,
//							this.coordArray[i].z+p.z/size));
//					j++;
//				}
//				normals = null;
//				
//				Shape3D normal = new Shape3D(la,blueAppearance);
//				normal.setName(curname.concat("Norms"));
//				
//				group.addChild(normal);
//				scene.addNamedObject(curname, normal);
			}
		}
		
		// blueAppearance!
		final Appearance blueAppearance = new Appearance();
		LineAttributes lineAttributes = new LineAttributes();
		lineAttributes.setLineWidth(2.0f);
		ColoringAttributes blueColoringAttributes = new ColoringAttributes();
		blueColoringAttributes.setColor(new Color3f(0.0f, 0.0f, 1.0f));
		blueAppearance.setColoringAttributes(blueColoringAttributes);
		blueAppearance.setLineAttributes(lineAttributes);
		PolygonAttributes bluePA = new PolygonAttributes();
		bluePA.setPolygonMode(PolygonAttributes.POLYGON_LINE);
		blueAppearance.setPolygonAttributes(bluePA);
		
		// Put BoundingBox
		
		Box bbCube = 
			new Box((float)(upper.x-lower.x) / 2 , 
							(float)(upper.y-lower.y) / 2 , 
							(float)(upper.z-lower.z) / 2 , 
							blueAppearance);
		Transform3D cubeTransform = new Transform3D();
		cubeTransform.setTranslation(new Vector3d(((upper.x-lower.x)/2)+lower.x, ((upper.y-lower.y)/2)+lower.y, ((upper.z-lower.z)/2)+lower.z));
		TransformGroup bbCubeTG = new TransformGroup(cubeTransform);
		bbCubeTG.addChild(bbCube);

		Box bbCube2 = 
				new Box((float)(upper2.x-lower2.x) / 2 , 
								(float)(upper2.y-lower2.y) / 2 , 
								(float)(upper2.z-lower2.z) / 2 , 
								blueAppearance);
		bbCubeTG.addChild(bbCube2);
		//group.addChild(bbCubeTG);		
		

		
		// Put geometry relative to curvatures
		for (int nbMaximums = 0; nbMaximums < maxMeanCurvIndices.length; nbMaximums++) {
			Sphere curvatureShape = new Sphere(0.4f);
			curvatureShape.setName("Sphere");
			curvatureShape.getChild(0).setName("S3D de Sphere");
		
			// System.out.println(this.coordArray[maxIndices[nbMaximums]]+" "+maxIndices[nbMaximums]);
			Transform3D sphereTransform = new Transform3D();
			sphereTransform.setTranslation(new Vector3f(this.coordArray[maxMeanCurvIndices[nbMaximums]]));
			TransformGroup sphereTransformGroup = new TransformGroup(sphereTransform);
			//curvatureShape.setAppearance(blueAppearance);
			sphereTransformGroup.addChild(curvatureShape);
			group.addChild(sphereTransformGroup);
			scene.addNamedObject("Meanguirlande", sphereTransformGroup);
		}
		ColoringAttributes greenColoringAttributes = new ColoringAttributes();
		greenColoringAttributes.setColor(new Color3f(0.0f, 1.0f, 0.0f));
		Appearance greenAppearance = new Appearance();
		greenAppearance.setColoringAttributes(greenColoringAttributes);
		for (int nbMaximums = 0; nbMaximums < maxGaussianCurvIndices.length; nbMaximums++) {
			Sphere curvatureShape = new Sphere(0.4f);
			curvatureShape.setName("Sphere");
			curvatureShape.getChild(0).setName("S3D de Sphere");
		
			// System.out.println(this.coordArray[maxIndices[nbMaximums]]+" "+maxIndices[nbMaximums]);
			Transform3D sphereTransform = new Transform3D();
			sphereTransform.setTranslation(new Vector3f(this.coordArray[maxGaussianCurvIndices[nbMaximums]]));
			TransformGroup sphereTransformGroup = new TransformGroup(sphereTransform);
			sphereTransformGroup.addChild(curvatureShape);
			//curvatureShape.setAppearance(greenAppearance);
			group.addChild(sphereTransformGroup);
			scene.addNamedObject("Gaussianguirlande", sphereTransformGroup);
		}

		return scene;
	} // End of quickMakeScene


	/**
	 * Reads the Mean and Gaussian curvatures from the .cur file. 
	 */
	private void readCurvatures() {
		//importCurvatures::begin
		RandomAccessFile infile = null;
    
		try {
			infile = new RandomAccessFile(this.fileName,"r");
		} catch (FileNotFoundException exception) {
			exception.printStackTrace();
		}
		
		if (infile != null) {	
			try {
				infile.seek(0);
			} catch (IOException exception) {
				exception.printStackTrace();
			}
			
      // readCurvatures(infile)::begin
			this.curvArray = new double[this.coordArray.length][2];
			try {
				for (int i = 0; i < this.coordArray.length; i++) {
					String line = infile.readLine();
					
		  	 	this.curvArray[i][0] = 
		  	 			new Double(line.substring(0, line.indexOf(" ")).trim());
		    	this.curvArray[i][1] = 
		    			new Double(line.substring(line.indexOf(" ")).trim());
		    }
    	} catch (EOFException e) {
        //System.out.println("j'ai fini de lire ce fichier !");
			} catch (IOException exception) {
			}// readCurvatures(infile)::end
		}
		
		if (infile != null) {
      try {
				infile.close();
			} catch (IOException exception) {
				exception.printStackTrace();
			}
    } // importCurvatures::end
		
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis() - time;
			System.out.println("reading Curvatures: " + time + " ms");
			time = System.currentTimeMillis();
		}
//		for (double[] curvature : this.curvatures) {
//			System.out.println(curvature[0]+" "+curvature[1]);
//		}
		
	}


	/**
	 * Creates the Mean and Gaussian curvatures using JtsMesh. 
	 */
	private void createCurvatures() {
		JtsMesh mesh = new JtsMesh(coordArray, coordIdxList);
		this.curvArray = mesh.jtsDoubleCurvatures();
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis() - time;
			System.out.println("jtsDouble: " + time + " ms");
			time = System.currentTimeMillis();
		}
//		for (double[] curvature : this.curvatures) {
//			System.out.println(curvature[0]+" "+curvature[1]);
//		}
		
		//exportCurvatures::begin
		RandomAccessFile infile = null;  
		try {
			infile = new RandomAccessFile(this.fileName,"rw");
		} catch (FileNotFoundException exception) {
			exception.printStackTrace();
		}
		
		if (infile != null) {
      try {
				infile.setLength(0);
			} catch (IOException exception) {
				exception.printStackTrace();
			}

      // writeCurvatures(infile)::begin
			for (int i = 0; i < coordArray.length; i++) {
		    try  {
	        infile.writeBytes(this.curvArray[i][0]+" "+this.curvArray[i][1]+"\n");
	      }
	      catch (IOException exception) {
	      	exception.printStackTrace();
	      }
			}	// writeCurvatures(infile)::end
    }
		
		if (infile != null) {
      try {
				infile.close();
			} catch (IOException exception) {
				exception.printStackTrace();
			}
    } // exportCurvatures::end
		
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis() - time;
			System.out.println("writing Curvatures: " + time + " ms");
			time = System.currentTimeMillis();
		}
	}

	
	/**
	 * Each face is converted to triangles.  As each face is converted,
	 * we look up which geometry group and smoothing group the face
	 * belongs to.  The generated triangles are added to each of these
	 * groups, which are also being converted to a new triangle based format.
	 *
	 * We need to convert to triangles before normals are generated
	 * because of smoothing groups.  The faces in a smoothing group 
	 * are copied into a GeometryInfo to have their normals calculated,
	 * and then the normals are copied out of the GeometryInfo using
	 * GeometryInfo.getNormalIndices.  As part of Normal generation,
	 * the geometry gets converted to Triangles.  So we need to convert
	 * to triangles *before* Normal generation so that the normals we
	 * read out of the GeometryInfo match up with the vertex data
	 * that we sent in.  If we sent in TRIANGLE_FAN data, the normal
	 * generator would convert it to triangles and we'd read out 
	 * normals formatted for Triangle data.  This would not match up
	 * with our original Fan data, so we couldn't tell which normals
	 * go with which vertices.
	 */
	private void quickConvertToTriangles() {
		boolean triangulate = (flags & TRIANGULATE) != 0;
		boolean normals = !normList.isEmpty() && !normIdxList.isEmpty() &&
		(normIdxList.size() == coordIdxList.size());
		int numFaces = stripCounts.size();
		boolean haveSgroups = curSgroup != null;

		triGroups = new HashMap<String, ArrayList<Integer>>(50);
		if (haveSgroups) triSgroups = new HashMap<String, ArrayList<Integer>>(50);

		ArrayList<Integer> newCoordIdxList = null;
		ArrayList<Integer> newNormIdxList = null;
		
		if (triangulate) {
			GeometryInfo gi = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
			gi.setStripCounts(objectToIntArray(stripCounts));
			gi.setCoordinates(coordArray);
			gi.setCoordinateIndices(objectToIntArray(coordIdxList));
			if (normals) {
				gi.setNormals(normArray);
				gi.setNormalIndices(objectToIntArray(normIdxList));
			}
			gi.convertToIndexedTriangles();

			// Data is now indexed triangles.  Next step is to take the data
			// out of the GeometryInfo and put into internal data structures

			int coordIndicesArray[] = gi.getCoordinateIndices();

			// Fix for #4366060
			// Make sure triangulated geometry has the correct number of triangles
			int tris = 0;
			for (int i = 0 ; i < numFaces ; i++)
				tris += ((Integer)stripCounts.get(i)).intValue() - 2;

			if (coordIndicesArray.length != (tris * 3)) {
				// Model contains bad polygons that didn't triangulate into the
				// correct number of triangles.  Fall back to "simple" triangulation
				triangulate = false;
			} else {
				int normIndicesArray[] = gi.getNormalIndices();

				// Convert index arrays to internal ArrayList format
				coordIdxList.clear();
				normIdxList.clear();
				for (int i = 0 ; i < coordIndicesArray.length ; i++) {
					coordIdxList.add(new Integer(coordIndicesArray[i]));
					if (normals) normIdxList.add(new Integer(normIndicesArray[i]));
				}
			}
		}

		if (!triangulate) {
			newCoordIdxList = new ArrayList<Integer>();
			if (normals) newNormIdxList = new ArrayList<Integer>();
		}
		
		// Repeat for each face in the model - add the triangles from each 
		// face to the Geometry and Smoothing Groups
		int baseVertex = 0;
		for (int f = 0 ; f < numFaces ; f++) {
			int faceSize = ((Integer)stripCounts.get(f)).intValue();

			// Find out the name of the group to which this face belongs
			Integer curFace = new Integer(f);
			curGroup = (String)groups.get(curFace);

			// Change to a new geometry group, create if it doesn't exist
			curTriGroup = triGroups.get(curGroup);
			if (curTriGroup == null) {
				curTriGroup = new ArrayList<Integer>();
				triGroups.put(curGroup, curTriGroup);
			}

			// Change to a new smoothing group, create if it doesn't exist
			if (haveSgroups) {
				curSgroup = (String)sGroups.get(curFace);
				if (curSgroup == null) {
					// Weird case - this face has no smoothing group.  Happens if the
					// first 's' token comes after some faces have already been defined.
					// Assume they wanted no smoothing for these faces
					curSgroup = "0";
				}
				curTriSgroup = triSgroups.get(curSgroup);
				if (curTriSgroup == null) {
					curTriSgroup = new ArrayList<Integer>();
					triSgroups.put(curSgroup, curTriSgroup);
				}
			}
			
			if (triangulate) {
				// Each polygon of n vertices is now n-2 triangles
				for (int t = 0 ; t < faceSize - 2 ; t++) {
					// The groups just remember the first vertex of each triangle
					Integer triBaseVertex = new Integer(baseVertex);
					curTriGroup.add(triBaseVertex);
					if (haveSgroups) curTriSgroup.add(triBaseVertex);

					baseVertex += 3;
				}
			} else {				
				// Triangulate simply
				for (int v = 0 ; v < faceSize - 2 ; v++) {
					// Add this triangle to the geometry group and the smoothing group
					Integer triBaseVertex = new Integer(newCoordIdxList.size());
					curTriGroup.add(triBaseVertex);
					if (haveSgroups) curTriSgroup.add(triBaseVertex);

					newCoordIdxList.add(coordIdxList.get(baseVertex));
					newCoordIdxList.add(coordIdxList.get(baseVertex + v + 1));
					newCoordIdxList.add(coordIdxList.get(baseVertex + v + 2));
					
					if (normals) {
						newNormIdxList.add(normIdxList.get(baseVertex));
						newNormIdxList.add(normIdxList.get(baseVertex + v + 1));
						newNormIdxList.add(normIdxList.get(baseVertex + v + 2));
					}

				}
				baseVertex += faceSize;
			}
		}
		
		// No need to keep these around
		stripCounts = null;
		groups = null;
		sGroups = null;

		if (!triangulate) {
			coordIdxList = newCoordIdxList;
			normIdxList = newNormIdxList;
		}
	} // End of quickConvertToTriangles

	
	/**
	 * Returns the BoundingBox of the object.
	 * 
	 * @return The limits of the object.
	 */
	public BoundingBox getBoundigBox() {
		// Find the limits of the model
		Point3f	minLimit = new Point3f(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE);
		Point3f	maxLimit = new Point3f(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE);
		for (int i = 0 ; i < this.coordArray.length ; i++) {

			// Keep track of limits for normalization
			if (coordArray[i].x < minLimit.x) minLimit.x = coordArray[i].x;
			if (coordArray[i].x > maxLimit.x) maxLimit.x = coordArray[i].x;
			if (coordArray[i].y < minLimit.y) minLimit.y = coordArray[i].y;
			if (coordArray[i].y > maxLimit.y) maxLimit.y = coordArray[i].y;
			if (coordArray[i].z < minLimit.z) minLimit.z = coordArray[i].z;
			if (coordArray[i].z > maxLimit.z) maxLimit.z = coordArray[i].z;
		}
		
		return new BoundingBox(new Point3d(minLimit), new Point3d(maxLimit)); 
	} // End of getBoundingBox
	
	/**
	 * Returns the Diagonal of the BoundingBox of the object.
	 * 
	 * @return The Diagonal of the limits of the object.
	 */
	public double getBoundigBoxDiagonal() {
		// Find the limits of the model
		Point3f	minLimit = new Point3f(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE);
		Point3f	maxLimit = new Point3f(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE);
		for (int i = 0 ; i < this.coordArray.length ; i++) {

			// Keep track of limits for normalization
			if (coordArray[i].x < minLimit.x) minLimit.x = coordArray[i].x;
			if (coordArray[i].x > maxLimit.x) maxLimit.x = coordArray[i].x;
			if (coordArray[i].y < minLimit.y) minLimit.y = coordArray[i].y;
			if (coordArray[i].y > maxLimit.y) maxLimit.y = coordArray[i].y;
			if (coordArray[i].z < minLimit.z) minLimit.z = coordArray[i].z;
			if (coordArray[i].z > maxLimit.z) maxLimit.z = coordArray[i].z;
		}
		
		return minLimit.distance(maxLimit); 
	} // End of getBoundingBoxDiagonal


	/**
	 * The Object File is loaded from the OBJ file specified by the filename.
	 */
	public void textLoad(String filename) 
			throws FileNotFoundException, IncorrectFormatException, ParsingErrorException {
		this.fileName = filename + ".cur";
		setBasePathFromFilename();

		Reader reader = new BufferedReader(new FileReader(filename));
			
		//quickTextLoad(reader);
		P3DMObjectFileParser st = new P3DMObjectFileParser(reader);

		coordList = new ArrayList<Point3f>();
		normList = new ArrayList<Vector3f>();
		coordIdxList = new ArrayList<Integer>();
		normIdxList = new ArrayList<Integer>();
		groups = new HashMap<Integer, String>(50);
		curGroup = "default";
		sGroups = new HashMap<Integer, String>(50);
		curSgroup = null;
		stripCounts = new ArrayList<Integer>();
		groupMaterials = new HashMap<String, String>(50);
		groupMaterials.put(curGroup, "default");
		materials = new P3DMObjectFileMaterials();
		
		time = 0L;
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis();
		}
		
		quickReadFile(st);
		
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis() - time;
			System.out.println("Read file: " + time + " ms");
			time = System.currentTimeMillis();
		}
	
		if ((flags & RECENTER) != 0) recenter();
		
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis() - time;
			System.out.println("recenter: " + time + " ms");
			time = System.currentTimeMillis();
		}
		
		//quickMakeScene();
		boolean gen_norms = normList.isEmpty() || normIdxList.isEmpty() || 
				(normIdxList.size() != coordIdxList.size());

		// Convert ArrayLists to arrays
		// moved to recenter
		if (!gen_norms) normArray = objectToVectorArray(normList);		

		quickConvertToTriangles();
		normList.clear();

		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis() - time;
			System.out.println("Convert to triangles: " + time + " ms");
			time = System.currentTimeMillis();
		}

		if ((gen_norms) && (curSgroup != null)) {
			smoothingGroupNormals();
			gen_norms = false;
			if ((DEBUG & 8) != 0) {
				time = System.currentTimeMillis() - time;
				System.out.println("Smoothing group normals: " + time + " ms");
				time = System.currentTimeMillis();
			}
		}

		// no more : "if (this.fromUrl)"
		File f = new File(this.fileName);
		if (f.exists()) {
			readCurvatures();
		} else {
			createCurvatures();
		}

		BranchGroup group = new BranchGroup();
		// Test files that contains multiple Shape3Ds but don't 
		// Each "Group" of faces in the model will be one Shape3D
		Iterator<String> e = triGroups.keySet().iterator();
		while (e.hasNext()) {
			String curname = e.next();
			ArrayList<Integer> triList = triGroups.get(curname);

			// Check for group with no faces
			if (triList.size() > 0) {
				GeometryInfo gi = new GeometryInfo(GeometryInfo.TRIANGLE_ARRAY);

				gi.setCoordinateIndices(groupIndices(coordIdxList, triList));
				gi.setCoordinates(coordArray);

				if ((DEBUG & 8) != 0) time = System.currentTimeMillis();
				if (gen_norms) {
					System.out.println("Pas de vn.");
					if ((flags & REVERSE) != 0) gi.reverse();
					NormalGenerator ng = new NormalGenerator(radians);
					ng.generateNormals(gi);
					normArray = new Vector3f[coordArray.length];
					normArray = gi.getNormals();
					int[] normIndices = new int[coordIdxList.size()];
					normIndices = gi.getNormalIndices();
					normIdxList = new ArrayList<Integer>(coordIdxList.size());
					for (int normIndex : normIndices) {
						normIdxList.add(new Integer(normIndex));
					}
					 
					if ((DEBUG & 8) != 0) {
						time = System.currentTimeMillis() - time;
						System.out.println("Generate normals: " + time + " ms");
						time = System.currentTimeMillis();
					}
				} else {
					gi.setNormalIndices(groupIndices(normIdxList, triList));
					gi.setNormals(normArray);
					if ((flags & REVERSE) != 0) gi.reverse();
				}

				// Put geometry into Shape3d
				Shape3D shape = new Shape3D();
				shape.setGeometry(gi.getGeometryArray(true, true, false));

				group.addChild(shape);

				if ((DEBUG & 8) != 0) {
					time = System.currentTimeMillis() - time;
					System.out.println("Shape 3D: " + time + " ms");
					time = System.currentTimeMillis();
				}
			}
		}
	} // End of textLoad(String)


	public void computingSignatures(int NB_FEATURE_POINTS) {
		// Clearing (Start)
		coordList.clear();
		normList.clear();
		coordIdxList.clear();
		normIdxList.clear();
		groupMaterials.clear();
		triGroups.clear();
		curTriGroup.clear();
		
		double maximum;
		BoundingBox boundingBox = this.getBoundigBox();
		System.out.println(boundingBox);
		double distanceMax = Double.MAX_VALUE;
		if (this.profileExtraction) {
			Point3d upper = new Point3d(), lower = new Point3d();
			boundingBox.getLower(lower);
			boundingBox.getUpper(upper);
			Point3d upper2 = new Point3d(upper), lower2 = new Point3d(lower);
			upper2.setX(upper2.x / 5);
			lower2.setX(lower2.x / 5);
			BoundingBox boundingBox2 = new BoundingBox(lower2, upper2);
			
			for (int i = 0; i < this.coordArray.length; i++) {
				if (!boundingBox2.intersect(new Point3d(this.coordArray[i]))) {
					this.curvArray[i][1] = 0;
					this.curvArray[i][0] = 0;
				}
			}
			
			distanceMax = (lower.distance(upper))/3;
			
			this.profileExtraction = false;
		}
		
		
		// Gaussian curvatures treatments !
		int[] maxGaussianCurvIndices = new int [NB_FEATURE_POINTS]; 
		for (int nbMaximums = 0; nbMaximums < NB_FEATURE_POINTS; nbMaximums++) {
			maximum = Double.MIN_VALUE;
			for (int i = 0; i < this.curvArray.length; i++) {
				if (this.curvArray[i][0] > maximum) {
					maxGaussianCurvIndices[nbMaximums] = i;
					maximum = this.curvArray[i][0];
				}
			}
			BoundingSphere bs = new BoundingSphere(
						new Point3d(this.coordArray[maxGaussianCurvIndices[nbMaximums]]), 0.5);
			if ((DEBUG & 128) != 0) {
				System.out.println("Maximum = "+this.curvArray[maxGaussianCurvIndices[nbMaximums]][0]+"\t"+maxGaussianCurvIndices[nbMaximums]);
			}
			for (int i = 0; i < this.coordArray.length; i++) {
				if (bs.intersect(new Point3d(this.coordArray[i]))) { 
					//TODO tester si la courbure est differente de 0 ?!?
					this.curvArray[i][0] = -500;
				}
			}
		}
		
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis() - time;
			System.out.println("Max Gaussian Curv: " + time + " ms");
			time = System.currentTimeMillis();
		}
		
		// Mean curvatures treatments !
		int[] maxMeanCurvIndices = new int [NB_FEATURE_POINTS]; 
		for (int nbMaximums = 0; nbMaximums < NB_FEATURE_POINTS; nbMaximums++) {
			maximum = Double.MIN_VALUE;
			for (int i = 0; i < this.curvArray.length; i++) {
				if (this.curvArray[i][1] > maximum) {
					maxMeanCurvIndices[nbMaximums] = i;
					maximum = this.curvArray[i][1];
				}
			}
			BoundingSphere bs = new BoundingSphere(
						new Point3d(this.coordArray[maxMeanCurvIndices[nbMaximums]]), 0.5);
			if ((DEBUG & 128) != 0) {
				System.out.println("Maximum = "+this.curvArray[maxMeanCurvIndices[nbMaximums]][1]+"\t"+maxMeanCurvIndices[nbMaximums]);
			}
			for (int i = 0; i < this.coordArray.length; i++) {
				if (bs.intersect(new Point3d(this.coordArray[i]))) { 
					//TODO tester si la courbure est differente de 0 ?!?
					this.curvArray[i][1] = 0;
				}
			}
		}
		
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis() - time;
			System.out.println("Max Mean Curv: " + time + " ms");
			time = System.currentTimeMillis();
		}
		
		// TODO MAX TO MAX !
		
		if ((DEBUG & 128) != 0) {
			for (int i = 0; i < NB_FEATURE_POINTS; i++)
				System.out.println(maxMeanCurvIndices[i]+" "+maxGaussianCurvIndices[i]);
		}
		
		// Clearing (continue)
		curvArray = null;
		
		// 3- Computing the signatures
		// gaussian Signature 
		ArrayList<Signature7> signaGaussianList = new ArrayList<Signature7>();
		for (int i = 0; i < NB_FEATURE_POINTS; i++) {
			Point3f P1 = this.coordArray[maxGaussianCurvIndices[i]];
			for (int j = i+1; j < NB_FEATURE_POINTS; j++) {
				Point3f P2 = this.coordArray[maxGaussianCurvIndices[j]];
				if (P1.distance(P2) > distanceMax)
					break;
				for (int k = j+1; k < NB_FEATURE_POINTS; k++) {
					Point3f P3 = this.coordArray[maxGaussianCurvIndices[k]];
					if ((P1.distance(P3) > distanceMax) || (P2.distance(P3) > distanceMax))
						break;
					Signature7 s = new Signature7();

					//I1
					s.nuplet[0] = P1.distance(P2);	
					//I2
					s.nuplet[1] = P2.distance(P3);
					//I3
					s.nuplet[2] = P1.distance(P3);
					
					//r, n_t
					Vector3f 	r = new Vector3f(), n_t = new Vector3f(), 
										P1P2 = new Vector3f(), P1P3 = new Vector3f(),
										aux = new Vector3f(),
										N1 = this.normArray[maxGaussianCurvIndices[i]],
										N2 = this.normArray[maxGaussianCurvIndices[j]];
									//N3 = this.normArray[maxGaussianCurvIndices[k]];
					Point3f negP1 = new Point3f(P1);
					negP1.negate();
					P1P2.add(P2, negP1);
					P1P3.add(P3, negP1);
					r.normalize(P1P2);
					n_t.cross(P1P2, P1P3);
					n_t.normalize();
					
					
					float ntn1 = n_t.dot(N1),
								ntn2 = n_t.dot(N2);
					
					//J1
					aux.cross(n_t, r);
					s.nuplet[3] = aux.dot(N1) / ntn1;					
					//J2
					s.nuplet[4] = aux.dot(N2) / ntn2;					
					//J~1
					s.nuplet[5] = r.dot(N1) / ntn1;					
					//J~2
					s.nuplet[6] = r.dot(N2) / ntn2;
					
					signaGaussianList.add(s);
				}
			}
		}
		System.out.println("nb = "+signaGaussianList.size());
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis() - time;
			System.out.println("Calculating Gaussian Signature: " + time + " ms");
			time = System.currentTimeMillis();
		}
		ArrayList<Signature7> signaMeanList = new ArrayList<Signature7>();
		for (int i = 0; i < NB_FEATURE_POINTS; i++) {
			Point3f P1 = this.coordArray[maxMeanCurvIndices[i]];
			for (int j = i+1; j < NB_FEATURE_POINTS; j++) {
				Point3f P2 = this.coordArray[maxMeanCurvIndices[j]];
				if (P1.distance(P2) > distanceMax)
					break;
				for (int k = j+1; k < NB_FEATURE_POINTS; k++) {
					Point3f P3 = this.coordArray[maxMeanCurvIndices[k]];
					if ((P3.distance(P2) > distanceMax) || (P1.distance(P3) > distanceMax))
						break;
					Signature7 s = new Signature7();
					//I1
					s.nuplet[0] = P1.distance(P2);					
					//I2
					s.nuplet[1] = P2.distance(P3);					
					//I3
					s.nuplet[2] = P1.distance(P3);
					
					//r, n_t
					Vector3f 	r = new Vector3f(), n_t = new Vector3f(),  
										P1P2 = new Vector3f(), P1P3 = new Vector3f(),
										aux = new Vector3f(),
										N1 = this.normArray[maxMeanCurvIndices[i]],
										N2 = this.normArray[maxMeanCurvIndices[j]];
									//N3 = this.normArray[maxMeanCurvIndices[k]];
					Point3f negP1 = new Point3f(P1);
					negP1.negate();
					P1P2.add(P2, negP1);
					P1P3.add(P3, negP1);
					r.normalize(P1P2);
					n_t.cross(P1P2, P1P3);
					n_t.normalize();
										
					float ntn1 = n_t.dot(N1),
								ntn2 = n_t.dot(N2);
					
					//J1
					aux.cross(n_t, r);
					s.nuplet[3] = aux.dot(N1) / ntn1;					
					//J2
					s.nuplet[4] = aux.dot(N2) / ntn2;					
					//J~1
					s.nuplet[5] = r.dot(N1) / ntn1;					
					//J~2
					s.nuplet[6] = r.dot(N2) / ntn2;
												
					signaMeanList.add(s);
				}	
			}
		}
		System.out.println("nb = "+signaMeanList.size());
		if ((DEBUG & 8) != 0) {
			time = System.currentTimeMillis() - time;
			System.out.println("Calculating Gaussian Signature: " + time + " ms");
			time = System.currentTimeMillis();
		}
		
		// Clearing (End)
		maxMeanCurvIndices = null;
		maxGaussianCurvIndices = null;
		coordArray = null;
		normArray = null;
		
		// 4- Saving the signature file
		//exportSignatures::begin
		RandomAccessFile infile = null;
		String str = ".sig"+NB_FEATURE_POINTS;
		this.fileName = this.fileName.substring(0, this.fileName.length()-4).concat(str);
		System.out.println(this.fileName);
		try {
			infile = new RandomAccessFile(this.fileName,"rw");
		} catch (FileNotFoundException exception) {
			exception.printStackTrace();
		}
		
		if (infile != null) {
      try {
				infile.setLength(0);
			} catch (IOException exception) {
				exception.printStackTrace();
			}
	
			// writeSignatures(infile)::begin
			for (int i = 0; i < signaGaussianList.size(); i++) {
				Signature7 s = signaGaussianList.get(i);
				//System.out.println(s);
		    try  {
	        infile.writeBytes(s.toString());
	      }
	      catch (IOException exception) {
	      	exception.printStackTrace();
	      }
			}	// writeSignatures(infile)::end
			
			if ((DEBUG & 8) != 0) {
				time = System.currentTimeMillis() - time;
				System.out.println("Writing Gaussian Signatures: " + time + " ms");
				time = System.currentTimeMillis();
			}
			
      // writeSignatures(infile)::begin
			for (int i = 0; i < signaMeanList.size(); i++) {
				Signature7 s = signaMeanList.get(i);
				//System.out.println(s);
		    try  {
	        infile.writeBytes(s.toString());
	      }
	      catch (IOException exception) {
	      	exception.printStackTrace();
	      }
			}	// writeSignatures(infile)::end
			
			if ((DEBUG & 8) != 0) {
				time = System.currentTimeMillis() - time;
				System.out.println("Writing Mean Signature: " + time + " ms");
				time = System.currentTimeMillis();
			}
    }
		
		if (infile != null) {
      try {
				infile.close();
			} catch (IOException exception) {
				exception.printStackTrace();
			}
    } // exportSignatures::end
		
		signaMeanList.clear();
		signaGaussianList.clear();		
	} // End of computingSignatures()


	public void setProfileExtraction(boolean profileExtraction) {
		this.profileExtraction = profileExtraction;
	}
	

} // End of class P3DMObjectFile

//End of file P3DMObjectFile.java